Notes on JavaScript

Resource

Eloquent JavaScript

现代 JavaScript 教程

基本类型

基本类型又叫做 primitive 原语

在 JavaScript 中,基本类型(基本数值、基本数据类型)是一种既非对象也无方法或属性的数据。有 7 种原始数据类型

null

除  null  之外的所有原始类型都可以通过  typeof  运算符进行测试。 typeof null  返回  "object" ,因此必须使用  === null  来测试  null

Introduction to JavaScript Runtime Environments | Codecademy

for… of

for...of 循环与 await 一起使用是因为 for...of 是同步的循环结构,它在每次迭代中都会等待 await 异步操作完成后再执行下一次迭代。这种行为与 JavaScript 的事件循环机制相符合。

map 方法则是数组的高阶函数,它会立即执行传入的回调函数,并且不会等待其中的异步操作完成,而是将所有异步操作同时启动。这意味着 map 方法不会等待其中的异步操作完成,而是立即返回一个包含所有 Promise 对象的新数组。

因此,直接在 map 方法中使用 await 是无效的,因为 map 方法本身并不是异步的,它不会等待回调函数中的异步操作完成。如果需要等待所有异步操作完成后再继续,可以使用 Promise.all 来处理。

number

Number.isFinite()

Number.isFinite() - JavaScript | MDN

NaN

NaN 不等于任何值包括它自身, 这意味着

NaN === NaN // false
Object.is(NaN,NaN) // true

string

字符串方法

去除字符串后缀
// 使用substring()方法
function removeSuffix(str, suffix) {
    if (str.endsWith(suffix)) {
        return str.substring(0, str.length - suffix.length);
    }
    return str;
}

// 使用slice()方法
function removeSuffix(str, suffix) {
    if (str.endsWith(suffix)) {
        return str.slice(0, -suffix.length);
    }
    return str;
}

// 示例用法
let myString = "example.jpg";
let suffix = ".jpg";
let result = removeSuffix(myString, suffix);
console.log(result); // 输出: "example"

对象

函数

函数是一种特殊的对象, 尽管 typeof func === 'function'

name 属性
length 属性

内建属性 “length”,它返回函数入参的个数

rest 参数不参与计数。

根据参数的类型,或者根据在我们的具体情景下的 length 来做不同的处理。这种思想在 JavaScript 的库里有应用。

return 和 throw

总的来说,throw 用于抛出异常,而 return 用于从函数中返回值。throw 会中断当前的程序流程,而 return 则会结束当前函数的执行并返回值。

数组

Array.prototype.forEach()

Array.prototype.forEach() - JavaScript | MDN

主要有三点

  • foreach 会跳过空元素
  • foreach 的 this 是从外部上下文确定的,最好还是搭配箭头函数用
  • foreach 不能用于 async
如何中断
  • return
  • try...catch

return 和 throw 都可以在 forEach 循环中起到中断循环的作用,但它们的行为和影响范围有所不同。return 用于中断当前迭代,throw 则可以中断整个循环

forEach 和 map
  • forEach 返回 undefined
  • map 返回新数组
Array.prototype.findIndex()

Array 实例的 findIndex() 方法返回数组中满足所提供的测试函数的第一个元素的索引。如果没有元素满足测试函数,则返回-1。

另请参见 find() 方法,该方法返回满足测试函数的第一个元素(而不是其索引)。

if (exist_token_index) 这一行,你应该使用 exist_token_index !== -1 而不是 exist_token_index,因为 findIndex 找不到元素时会返回 -1,而 -1 在布尔上下文中等同于 true

Array.prototype.flatMap()

flatMap() 方法对数组中的每个元素应用给定的回调函数,然后将结果展开一级,返回一个新数组。它等价于在调用 map() 方法后再调用深度为 1 的 flat() 方法(arr.map(...args).flat()),但比分别调用这两个方法稍微更高效一些。

Array.prototype.flatMap() - JavaScript | MDN

Array.prototype.find()

Array.prototype.find() - JavaScript | MDN

find 方法搜索的是使函数返回 true 的第一个(单个)元素。

Array. prototype.flat()

Array.prototype.flat() - JavaScript | MDN

Array 实例的 flat() 方法创建一个新数组,其中所有子数组元素递归地串联到指定深度

flat() 方法是一种复制方法。它不会改变 this ,而是返回一个浅拷贝,其中包含与原始数组中的元素相同的元素。

如果被展平的数组稀疏,则 flat() 方法会忽略空槽。例如,如果 depth 为 1,则根数组和第一层嵌套数组中的空槽都将被忽略,但进一步嵌套数组中的空槽将与数组本身一起保留。

Array.prototype.flat() - JavaScript | MDN

这意味着我们可以用 flat 方法过滤稀疏数组 ?

Array. prototype.reverse()

Array.prototype.reverse() - JavaScript | MDN

To reverse the elements in an array without mutating the original array, use toReversed().
要反转数组中的元素而不改变原始数组,请使用 toReversed()

reverse()  方法是通用的。它只期望  this  值具有  length  属性和整数键属性。虽然字符串也是类似于数组的,但这个方法不适用于它们,因为字符串是不可变的

Array.prototype.forEach()

Array.prototype.forEach() - JavaScript | MDN

map() 不同, forEach() 始终返回 undefined 并且不可链接。典型的用例是在链的末尾执行副作用。

There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool.
除了抛出异常之外,没有其他方法可以停止或中断 forEach() 循环。如果您需要这样的行为,那么 forEach() 方法是错误的工具。

Array.prototype.some()

Array.prototype.some() - JavaScript | MDN `

在数组中找到所提供的函数返回 true 的元素,则返回 true;否则返回 false。

返回值是布尔

Array.prototype.every()

Array.prototype.every() - JavaScript | MDN

reduce

#Todo

数组方法比较

同样适用于在 React 中使用数组方法的注意事项

会改变原数组会返回新数组
添加push, unshiftconcat, [...arr]
删除pop, shift, splicefilter, slice
替换splice, arr[i]=...map
排序reverse, sort[...arr], toSorted

This

在 JavaScript 中,函数的 this 关键字在运行时确定,而不是定义时。

箭头函数例外

丢失 this
  • 事件处理函数的回调
const obj = {
    name: 'John',
    sayName: function() {
        console.log(this.name);
    }
};

document.getElementById('myButton').addEventListener('click', obj.sayName); // 这里的this将会是按钮元素,而不是obj

解决方法可以是使用箭头函数,或者在回调函数中手动绑定 this。

  • 嵌套函数
const obj = {
    name: 'John',
    sayName: function() {
        setTimeout(function() {
            console.log(this.name); // 这里的this将会指向全局对象,而不是obj
        }, 1000);
    }
};

obj.sayName();

解决方法可以是在外部缓存 this,或者使用箭头函数。

  • 函数赋值
const obj = {
    name: 'John',
    sayName: function() {
        console.log(this.name);
    }
};

const myFunc = obj.sayName;
myFunc(); // 这里的this将会是全局对象,而不是obj

解决方法可以是使用.bind()方法绑定上下文,或者使用箭头函数。

  • 构造函数的方法
function Person(name) {
    this.name = name;
    this.sayName = function() {
        console.log(this.name);
    };
}

const john = new Person('John');
const sayName = john.sayName;
sayName(); // 这里的this将会是全局对象,而不是john

解决方法可以是将方法定义在构造函数的原型上,这样所有实例将共享同一个方法,并且能正确地绑定到实例上。

解决方案
  • 箭头函数-首选

箭头函数在定义时会捕获所在上下文的 this 值,并且在整个函数生命周期中保持不变。这意味着箭头函数内部的 this 是在箭头函数定义时绑定的,而不是在调用时决定的。这就是为什么箭头函数不会丢失上下文,因为它们已经在定义时绑定了正确的上下文。

const obj = {
    name: 'John',
    sayName: function() {
        setTimeout(() => {
            console.log(this.name); // 使用箭头函数解决this丢失的问题
        }, 1000);
    }
};

obj.sayName(); // 输出 "John"
  • bind()
const obj = {
    name: 'John',
    sayName: function() {
        setTimeout(function() {
            console.log(this.name);
        }. bind (this), 1000); // 使用bind()绑定上下文
    }
};

obj.sayName(); // 输出 "John"

  • 缓存 this
const obj = {
    name: 'John',
    sayName: function() {
        const self = this; // 缓存 this
        setTimeout (function() {
            console.log(self.name);
        }, 1000);
    }
};

obj.sayName(); // 输出 "John"



  • 构造函数中使用原型方法
function Person (name) {
    this.name = name;
}

Person.prototype. sayName = function() {
    console.log(this.name);
};

const john = new Person ('John');
john.sayName(); // 输出 "John"

类型检查

function checkType (value) {
    switch (typeof value) {
        case 'number':
            return isNaN (value) ? 'NaN' : isFinite (value) ? 'number' : value > 0 ? 'Infinity' : '-Infinity';
            // 尽管 Number.isFinite() 更可靠, 但是这里已经提前判断了类型是 number, 不用担心隐式转换
        case 'string':
        case 'boolean':
        case 'function':
        case 'undefined':
        return typeof value;
        case 'object':
            return value === null ? 'null' : Array.isArray(value) ? 'array' : ?  : 'object';
        default:
            return 'unknown';
    }
}

运算符

new

The new operator lets developers create an instance of a user-defined object type or of one of the built-in object types that has a constructor function.
new  运算符允许开发人员创建用户定义的对象类型或具有构造函数的内置对象类型之一的实例。

使用 new 调用的所有构造函数都将返回非基元( "object""function" )。大多数返回对象,但值得注意的例外是 Function ,它返回一个函数。

const str = new String ("String");
const num = new Number (100);

typeof str; // "object"
typeof num; // "object"

const func = new Function();

typeof func; // "function"

严格相等

严格相等会按照 IEEE 754 标准对 NaN、-0 和 +0 进行特殊处理(故 NaN != NaN,且 -0 == +0), 但不进行类型转换;如果类型不同,则返回 false;

Object.is() 既不进行类型转换,也不对 NaN、-0 和 +0 进行特殊处理(这使它和 === 在除了那些特殊数字值之外的情况具有相同的表现)

Equality comparisons and sameness - JavaScript | MDN

Object.is() 不进行类型转换,也不对 NaN-0+0 进行特殊处理(使其具有与 === 除了那些特殊的数值)。

相等和严格相等

普通的相等性检查  ==  存在一个问题,它不能区分出  0  和  false: 也同样无法区分空字符串和  false

严格相等运算符  ===  在进行比较时不会做任何的类型转换。

typeof

typeof - JavaScript | MDN

例外
typeof null // "object"

instanceof

instanceof  操作符用于检查一个对象是否属于某个特定的 class。同时,它还考虑了继承。

检测机制

基于原型的检测机制

  • 如果这儿有静态方法  Symbol. hasInstance,那就直接调用这个方法:
  • 大多数 class 没有  Symbol. hasInstance。在这种情况下,标准的逻辑是:使用  obj instanceOf Class  检查  Class. prototype  是否等于  obj  的原型链中的原型之一。
typeofinstanceof

typeof 检查的是原始值

instanceof 检查的是一个值是否是类或者构造函数的实例

The  typeof  operator checks if a value has type of primitive type which can be one of  booleanfunctionobjectnumberstringundefined  and  symbol  (ES6).

The  instanceof  operator checks if a value is an instance of a class or constructor function.

参考

空值合并运算符

result = (a !== null && a !== undefined) ? a : b;

result = a ?? b
??||
  • || 返回第一个 值。
  • ?? 返回第一个 已定义的 值。

|| 无法区分 false0、空字符串 ""null/undefined。它们都一样 —— 假值(falsy values)。

全局函数

isNaN()

isNaN() - JavaScript | MDN

isNaN() 函数确定某个值是否为 NaN ,如有必要,首先将该值转换为数字。由于 isNaN() 函数内的强制转换可能会令人惊讶,因此您可能更喜欢使用 Number.isNaN()

可以使用 Number.isNaN() 来替代 typeofisNaN() 的组合。

异步编程和事件循环

Promise

Promise - JavaScript | MDN

promise 是一个对象

  • 示例
async function fetchData() {
    console.log ("Fetching data...");
    // 模拟异步操作,比如发起网络请求
    await new Promise (resolve => setTimeout (resolve, 1000));
    console. log ("Data fetched!");
    return "Data";
}

async function main() {
    console. log ("Start");

    // 使用 await 等待异步函数执行完成
    const data = await fetchData();

    console. log ("Received data: ", data);
    console. log ("End");
}

console. log ("Before main");
main();
console. log ("After main");

Before main
Start
Fetching data...
After main
Data fetched!
Received data: Data
End
  1. 首先打印出 “Before main”,然后调用 main() 函数。
  2. main() 函数中,首先打印出 “Start”。
  3. 接着调用 fetchData() 函数,它内部包含了一个异步操作(模拟网络请求),打印出 “Fetching data…”。此时,JavaScript 引擎暂停 main() 函数的执行,并继续执行后面的同步代码。
  4. 打印出 “After main”。
  5. 1 秒后,异步操作完成,打印出 “Data fetched!”。
  6. 继续执行 fetchData() 函数中的代码,返回数据 “Data”。
  7. main() 函数中的 await fetchData() 表达式得到了解决(resolved),并将结果赋给 data 变量。
  8. 打印出 “Received data: Data” 和 “End”。

错误处理

try… catch

try {
  const response = await fetch ('https://example.com');
  if (! response. ok) {
    throw new Error ('Failed to fetch data');
  }
  // Process the response if successful
} catch (error) {
  console. error ('Error: ', error);
}
try… catch 和 promise. catch

使用 try... catch 的方式和使用 promise. catch 有几个关键的区别:

  1. 适用场景
    • try... catch 适用于同步代码块中捕获异常,包括异步代码中的同步部分。
    • promise. catch 适用于处理 Promise 对象中的 rejected 状态,特别是在异步操作中。
  2. 错误捕获范围
    • 使用 try... catch 可以捕获到整个 try 块内部的同步和部分异步代码中的错误。
    • 使用 promise. catch 只能捕获到 Promise 链中出现的错误,而且仅限于 Promise 的 rejected 状态。
  3. 错误堆栈
    • try... catch 可以捕获到整个代码块的错误,并提供完整的错误堆栈。
    • promise. catch 只能捕获到 Promise 链中的错误,错误堆栈可能相对较小。
  4. 代码结构
    • 使用 try... catch 可以更容易地与同步代码集成,并允许你在异步代码中的同步部分中使用。
    • 使用 promise. catch 更符合 Promise 链式调用的结构,更适合处理异步操作中的错误。
  5. 可读性
    • try... catch 通常用于同步代码中,能够更清晰地表达同步操作中的错误处理。
    • promise. catch 在处理异步操作时更为常见,更容易阅读和理解异步操作的错误处理逻辑。
try… catch 捕获 rejected promise
async function fetchData() {
  try {
    const response = await fetch ('https://example.com');
    const data = await response.json();
    return data;
  } catch (error) {
    console. error ('Error: ', error);
  }
}

fetchData();

在这个例子中,fetchData 是一个异步函数,使用了 async 关键字,其中的 await 关键字会暂停函数执行,直到 Promise 被解决(resolved)。如果 fetch 请求失败(例如,网络错误或者响应状态码不是 200),await fetch 将返回一个 rejected 的 Promise,然后 catch 语句就会捕获到这个错误。因为在 await 之前有一个 try 块,所以该错误会被 try… catch 捕获到。

标准内置对象

Reflection 反射

Promise

Promise - JavaScript | MDN

Promises/A+

ES 6+ 新特性

Spread 语法

Spread 语法内部使用了迭代器来收集元素,与  for.. of  的方式相同。

解构赋值

解构赋值语法是一种 Javascript 表达式。可以将数组中的值或对象的属性取出,赋值给其他变量。

箭头函数

Arrow function expressions - JavaScript | MDN

箭头函数表达式是传统函数表达式的紧凑替代品

In older versions of JavaScript, you would have had to use the bind method, which explicitly sets this. This pattern can be found often in some earlier versions of frameworks, like React, before the advent of ES6 在旧版本的 javascript 中, 你将需要使用 bind 方法来显式绑定 this

However, since an object does not create a new lexical scope, an arrow function will look beyond the object for the value of this.

如果我们在箭头函数中访问  arguments,访问到的  arguments  并不属于箭头函数,而是属于箭头函数外部的“普通”函数。

箭头函数没有自身的  this。现在我们知道了它们也没有特殊的  arguments  对象。

箭头函数中的 this 关键字的处理方式与普通函数不同。在箭头函数中,this 的值是在创建函数时确定的,而不是在调用时确定的。箭头函数捕获了其所在上下文中的 this 值,并且在整个函数生命周期中保持不变。

这意味着箭头函数中的 this 与定义它的上下文中的 this 相同,而不是调用它的上下文。这通常是很有用的,因为它允许开发者在回调函数中轻松访问外部函数的 this,而不必担心它在运行时会改变。

参考

原型

原型是 JavaScript 对象相互继承功能的一种机制。

JavaScript 中的每个对象都有一个内置属性,称为原型。原型本身就是一个对象,因此原型将有自己的原型,形成所谓的原型链。当我们到达一个具有 null 作为自己原型的原型时,链就结束了

注意:指向其原型的对象属性不称为 prototype 。它的名称并不标准,但实际上所有浏览器都使用 __proto__ 。访问对象原型的标准方法是 Object.getPrototypeOf() 方法。

Array.isArray()instanceof Array

Array.isArray() checks if the passed value is an Array. It does not check the value’s prototype chain, nor does it rely on the Array constructor it is attached to. It returns true for any value that was created using the array literal syntax or the Array constructor. This makes it safe to use with cross-realm objects, where the identity of the Array constructor is different and would therefore cause instanceof Array to fail.
Array.isArray()  检查传递的值是否为  Array 。它不检查值的原型链,也不依赖于它所附加的  Array  构造函数。对于使用数组文字语法或  Array  构造函数创建的任何值,它都会返回  true 。这使得它可以安全地用于跨领域对象,其中  Array  构造函数的标识不同,因此会导致  instanceof Array  失败。

Array.isArray() also rejects objects with Array.prototype in its prototype chain but aren’t actual arrays, which instanceof Array would accept.
Array.isArray()  还拒绝其原型链中带有  Array.prototype  但实际上不是数组的对象,而 instanceof Array  却会接受的(true)。

ECMAScript® 2023 Language Specification

If argument is not an Object, return false.
2. If argument is an Array exotic object, return true.
3. If argument is a Proxy exotic object, then

    a. Perform ? ValidateNonRevokedProxy(argument).
    b. Let proxyTarget be argument.[[ProxyTarget]].
    c. Return ? IsArray(proxyTarget).

4. Return false.

参考